package de.cau.apo.problems.unrelatedscheduling;

import java.util.Arrays;
import java.util.Comparator;

import de.cau.apo.container.BitIndexSet;
import de.cau.apo.exceptions.NotFeasibleException;
import de.cau.apo.problems.AbstractSolution;
import de.cau.apo.problems.unrelatedscheduling.Job;

/**
 * This class represents a possible solution of the unrelated scheduling problem.
 * It contains the available machines and is responsive for checking the feasibility
 * and calculating the objective value.
 * 
 * @author Marius Richter
 */
public class USSolution extends AbstractSolution<USInstance> {
    
    // ########### properties ###########
    
	/** the array of machines that are available in this solution */
    public Machine[] machines;
    
    // ########### initialization ###########
    
    /**
     * Creates a new empty solution for the unrelated scheduling problem.
     * @param instance the instance for that this solution shall be created
     */
    public USSolution(USInstance instance) {
    	super(instance);
        this.machines = new Machine[instance.getNumberMachines()];
        clear();
    }
    
    // ########### getter/setter ###########
    
    /**
     * @param machineIndex
     * @return the machine that was created at the given index
     */
    public Machine getMachine(int machineIndex) {
    	Machine machine = machines[machineIndex];
    	if (machine.getIndex() != machineIndex) {
    		for (Machine m : machines) {
    			if (m.getIndex() == machineIndex) {
    				return m;
    			}
    		}
    	}
    	return machine;
    }
    
    /**
     * @return the array of available machines
     */
    public Machine[] getMachines() {
		return machines;
	}
    
    /**
     * add the job to the specified machine
     * @param jobIndex the index of the job
     * @param machineIndex the index of the machine
     */
    public void addJob(int jobIndex, int machineIndex) {
    	Job job = this.getInstance().getJob(jobIndex);
    	for (Machine machine : machines) {
    		machine.removeJob(job);
    	}
        this.getMachine(machineIndex).addJob(job);
    }
    
    /**
     * remove the job from the given machine
     * @param jobIndex the index of the job
     * @param machineIndex the index of the machine
     */
    public void removeJob(int jobIndex, int machineIndex) {
    	this.getMachine(machineIndex).removeJob(this.getInstance().getJob(jobIndex));
    }
    
    /**
     * remove all jobs from the machine (set them to -1)
     * @param machineIndex the index of the machine
     */
    public void clearMachine(int machineIndex) {
    	this.machines[machineIndex].clear();
    }
    
    /**
     * remove all jobs from all machines (set all jobs to -1)
     */
    public void clear() {
        for (int i = 0;i < this.machines.length;i++) {
            this.machines[i] = new Machine(i);
        }
    }
    
    // ########### AbstractSolution implementation ###########
    
    /**
     * {@inheritDoc}
     */
    @Override
    public void isFeasible() throws NotFeasibleException {
    	BitIndexSet jobs = new BitIndexSet(getInstance().getNumberJobs());
        for (Machine machine : this.machines) {
            for (Job job : machine.getJobs()) {
            	if (job.getSize(machine) == -1) {
            		throw new NotFeasibleException(job + " must not be assigned to " + machine);
            	}
            	jobs.add(job.getIndex());
            }
        }
        
        if (this.getInstance().getNumberJobs() != jobs.size()) {
            throw new NotFeasibleException("Not all jobs are assigned to a machine");
        }
    }
    
    /**
	 * {@inheritDoc}
	 */
	@Override
	public void decode(String solution) {
		this.clear();
		String[] jobAssignments = solution.split(" ");
		int job = 0;
		for (String jobAssignment : jobAssignments) {
			if (!jobAssignment.equals("#")) {
				this.addJob(job, Integer.parseInt(jobAssignment));
			}
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public String encode() {
		return this.toString();
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public double getObjValue() {
        int makespan = 0;
        for (Machine machine : this.machines) {
            makespan = Math.max(makespan, machine.getMakespan());
        }
        return makespan;
	}
	
	// ########### overriding toString() ###########

    @Override
    public String toString() {
        String string = "";

        Job[] jobs = Arrays.copyOf(this.getInstance().getJobs(), this.getInstance().getNumberJobs());
        Arrays.sort(jobs, new Comparator<Job>(){
			@Override
			public int compare(Job arg0, Job arg1) {
				return arg0.index - arg1.index;
			}
        });
        
        for (Job job : jobs) {
        	Machine assignedMachine = null;
            for (Machine machine : machines) {
            	if (machine.jobs.contains(job)) {
            		assignedMachine = machine;
            		break;
            	}
            }
            if (assignedMachine == null) {
                string += "# ";
            } else {
                string += assignedMachine.getIndex() + " ";
            }
        }
        
        return string;
    }

}
